#include <retro.h>
#include "retro.creator.h"

using namespace Retro::Creator;
using namespace Retro::Creator::Editor;
using namespace Retro::Vector;
using namespace Retro::Device::Display;
using namespace SPLib::Geom;

#pragma region Macros
#define SPRITEMAP           ((Data::SpriteMap*)getMap())

#define ANCHORSIZE          10L
#define SNAP_THRESHOLD      10

#define IsAddSelection()    (GetKeyState(VK_SHIFT) >> 1)
#define IsRemoveSelection() (GetKeyState(VK_CONTROL) >> 1)
#define IsCopySelection()   (GetKeyState(VK_CONTROL) >> 1)
#define IsFreeSelection()   (GetKeyState(VK_MENU) >> 1)

#define SetMapEdited() \
  getMap()->setUnsaved(); \
  sendNotify(EditorNotifyEdit); \
  invalidate(FALSE);

#define SPRITEUNDO_ADD          0x0001
#define SPRITEUNDO_DELETE       0x0002
#define SPRITEUNDO_EDIT         0x0003
#define SPRITEUNDO_SORT         0x0004

#define SPRITEUNDOFLAG_DELETED  0x0001
#pragma endregion

#pragma region Helper Methods
INT findMargin(INT value, INT grid)
{
  INT begin = value % grid;

  if (!begin) return 0;

  INT end = grid - begin;

  return (begin < end) ? begin : -end;
}

INT findMargin(INT left1, INT right1, INT left2, INT right2, BOOL& isFar)
{
  INT margins[] = {
    left2 - left1,
    left2 - right1,
    right2 - left1,
    right2 - right1
  };

  INT margin = 0;

  for (int i = 1; i < 4; ++i)
  {
    if (abs(margins[i]) < abs(margins[margin])) margin = i;
  }

  isFar = margin % 2;
  return margins[margin];
}

INL BOOL isSelectable(Data::SpriteData* value)
{
  return !(value->flags & (SPRITENODE_HIDDEN | SPRITENODE_LOCKED));
}
#pragma endregion

// CREATE & DESTROY -----------------------------------------------------------

SpriteEditor::SpriteEditor() :
  MapEditor(Data::ResourceSpriteMap)
{
  resize        = AnchorNone;
  mSnapToGrid   = FALSE;

  mOvered       = NULL;
  firstNode     = NULL;
  copy          = NULL;

  undoNode      = NULL;
  undoCurr      = NULL;
  undoEdit      = NULL;

  mNodeResource = NULL;
  mNodeType     = Data::SpriteTexture;

  for (UINT i = 0; i < ARRAYSIZE(mNodeColors); ++i)
    mNodeColors[i].value = 0xFFFFFFFF;
}

// USER INPUT -----------------------------------------------------------------

VOID  SpriteEditor::onMouseDown(Event::MouseEvent& iEvent)
{
  if (!getMap()) return;

  adjustMousePos(iEvent.point);
  focus();

  switch (getTool())
  {
  case SpriteToolPen:
    switch (iEvent.mkButton)
    {
    case MK_LBUTTON:
      addNode(mNodeSource, Bounds::get(
        mNodeSource.size / -2L,
        mNodeSource.size));
      break;
    }
    break;
  case SpriteToolRect:
  case SpriteToolTile:
    switch (iEvent.mkButton)
    {
    case MK_LBUTTON:
      addNode(mNodeSource, Bounds::get(0, 0, 1, 1));
      break;
    }
    break;
  case SpriteToolSelect:
    if (isSpriteSelected(mOvered))
    {
      if (IsAddSelection())
      {
        deselectNode(mOvered);
      }
      else
      {
        Geom::Bounds wAnchor;

        mCurrPoint = mDragPoint = iEvent.point;

        for (int i = (int)AnchorFirst; i <= (int)AnchorLast; ++i)
        {
          wAnchor = mOvered->bounds;
          getAnchorBounds(wAnchor, (Anchor)i);

          if (wAnchor.contains(iEvent.point))
          {
            switch ((Anchor)i)
            {
              case AnchorNW:
              case AnchorNE:
              case AnchorSW:
              case AnchorSE:
                mSelectMode = SelectModeResize;
                resize = (Anchor)i;
                break;
              case AnchorAxisX:
              case AnchorAxisY:
                mSelectMode = SelectModeRotate;
                break;
            }

            return;
          }
        }

        mSelectMode = SelectModeDrag;
      }

      return;
    }

    if (mOvered)
    {
      selectNode(mOvered);

      if (!IsAddSelection())
      {
        deselectAll();
        selectNode(mOvered);
      }

      mSelectMode = SelectModeDrag;
      mCurrPoint = mDragPoint = iEvent.point;
    }
    else
    {
      if (!IsAddSelection() && !IsRemoveSelection())
        deselectAll();

      mSelectMode = SelectModeSelect;
      mCurrPoint = mDragPoint = iEvent.point;

      mSelection.point = mDragPoint;
      mSelection.size = Size::get(0, 0);
    }
    break;
  }
}

VOID  SpriteEditor::onMouseUp(Event::MouseEvent& iEvent)
{
  if (!getMap()) return;

  adjustMousePos(iEvent.point);
  mCurrPoint = iEvent.point;

  switch (getTool())
  {
  case SpriteToolRect:
    undoCurr->node->bounds = undoCurr->node->viewBounds;
    invalidate(FALSE);
    break;
  case SpriteToolSelect:
    switch (mSelectMode)
    {
    case SelectModeSelect:
      {
        SpriteNode* node = firstNode[mLayer];
        Bounds select = mSelection;

        select.absolute();

        while (node) {
          Bounds nodeBounds = node->bounds;
          nodeBounds.absolute();

          if (isSelectable(node) && select.intersects(nodeBounds)) {
            BOOL selected = isSpriteSelected(node);

            if (IsRemoveSelection()) {
              if (selected) deselectNode(node);
            } else {
              if (!selected) selectNode(node);
            }
          }

          node = node->next;
        }

        invalidate(FALSE);
      }
      break;
    case SelectModeDrag:
    case SelectModeResize:
      if (!(iEvent.point - mDragPoint).isEmpty())
      {
        SpriteNode* sel = firstNode[mLayer];

        while (sel)
        {
          if (isSpriteSelected(sel))
          {
            if (IsCopySelection())
            {
              SpriteNode *node = new SpriteNode;

              memcpy(node, sel, sizeof(SpriteNode));
              node->child = node->prev = node->next = NULL;
              node->flags &= ~SPRITENODE_SELECTED;

              if (node->texture)
                node->texture->ref();

              insertNode(node, sel->prev);
              appendUndo(node, UndoAdd);
            }

            appendUndo(sel, UndoEdit);
            sel->bounds = sel->viewBounds;
          }

          sel = sel->next;
        }

        closeUndo();
      }
      break;
    case SelectModeRotate:
      if (!(iEvent.point - mDragPoint).isEmpty()) {
        SpriteNode* sel = firstNode[mLayer];

        while (sel) {
          if (isSpriteSelected(sel)) {
            if (IsCopySelection()) {
              SpriteNode *node = new SpriteNode;

              memcpy(node, sel, sizeof(Data::SpriteData));
              node->child = NULL;
              node->flags &= ~SPRITENODE_SELECTED;

              insertNode(node, sel->prev);
              appendUndo(node, UndoAdd);
            }

            appendUndo(sel, UndoEdit);
            sel->rotate = sel->viewRotate;
          }

          sel = sel->next;
        }

        closeUndo();
      }
      break;
    }
    break;
  }

  mSelectMode = SelectModeNone;
}

VOID  SpriteEditor::onMouseMove(Event::MouseEvent& iEvent)
{
  if (!getMap()) return;

  Frame::get()->getSurface()->scrollToPoint(iEvent.point);
  adjustMousePos(iEvent.point);

  mCurrPoint  = iEvent.point;
  snapNodeX   = NULL;
  snapNodeY   = NULL;

  switch (getTool())
  {
  case SpriteToolRect:
  case SpriteToolTile:
    if (iEvent.mkButton == MK_LBUTTON && undoCurr && undoCurr->node)
    {
      undoCurr->node->viewBounds.size = Size(
        mCurrPoint - undoCurr->node->viewBounds.point);
      undoCurr->node->bounds = undoCurr->node->viewBounds;
    }
  case SpriteToolPen:
    mDragPoint.setEmpty();
    invalidate(FALSE);
    break;
  case SpriteToolSelect:
    switch (mSelectMode)
    {
    case SelectModeNone:
      {
        SpriteNode* node = firstNode[mLayer];
        SpriteNode* over = NULL;

        while (node && node->next)
          node = node->next;

        while (node)
        {
          if (hitTestNode(node, iEvent.point))
          {
            if (isSpriteSelected(node))
            {
              over = node;
              break;
            }
            else if (!over)
            {
              over = node;
            }
          }

          node = node->prev;
        }

        if (mOvered != over)
        {
          mOvered = over;
          invalidate(FALSE);
        }
      }
      break;
    case SelectModeSelect:
      mSelection.size = Size(mCurrPoint - mDragPoint);
      invalidate(FALSE);
      break;
    case SelectModeDrag:
    case SelectModeResize:
      {
        SpriteNode* node = firstNode[mLayer];

        mOvered->viewBounds = mOvered->bounds;
        calcEditBounds(mOvered->viewBounds, mSelectMode);

        while (node) {
          if (node != mOvered && isSpriteSelected(node)) {
            node->viewBounds = node->bounds;
            node->viewBounds += (mOvered->viewBounds - mOvered->bounds);
          }

          node = node->next;
        }

        invalidate(FALSE);
      }
      break;
    case SelectModeRotate:
      {
        SpriteNode* node = firstNode[mLayer];

        mOvered->viewRotate = mOvered->rotate;
        calcEditRotate(mOvered->viewBounds, mOvered->viewRotate, mSelectMode);

        while (node) {
          if (node != mOvered && isSpriteSelected(node)) {
            node->viewRotate = node->rotate;
            node->viewRotate += (mOvered->viewRotate - mOvered->rotate);
          }

          node = node->next;
        }

        invalidate(FALSE);
      }
      break;
    }
    break;
  }
}

VOID  SpriteEditor::onMouseLeave()
{
  if (!getMap()) return;

  mOvered      = NULL;
  snapNodeX   = NULL;
  snapNodeY   = NULL;

  getDevice()->getBuffer().clean();

  invalidate(FALSE);
}

VOID  SpriteEditor::onKeyDown(Event::KeyEvent& iEvent)
{
  if (!getMap()) return;

  SpriteNode* node;

  if (GetKeyState(VK_CONTROL) >> 1) {
    if (GetKeyState(VK_SHIFT) >> 1) {
      switch (iEvent.vkCode) {
      case VK_UP:
        node = firstNode[mLayer];

        while (node) {
          if (node->next && isSpriteSelected(node)) {
            appendUndo(node, UndoInsert);
            removeNode(node);
            insertNode(node, node->next);
          }

          node = node->next;
        }

        closeUndo();
        break;
      case VK_DOWN:
        node = firstNode[mLayer];

        while (node) {
          if (node->prev && isSpriteSelected(node)) {
            appendUndo(node, UndoInsert);
            removeNode(node);
            insertNode(node, node->prev->prev);
          }

          node = node->next;
        }

        closeUndo();
        break;
      case 'L':
        unlockSelection();
        break;
      case 'H':
        showSelection();
        break;
      case 'G':
        ungroupSelection();
        break;
      }
    } else {
      switch (iEvent.vkCode) {
      case 'A':
        selectAll();
        break;
      case 'L':
        lockSelection();
        break;
      case 'H':
        hideSelection();
        break;
      case 'G':
        groupSelection();
        break;
      }
    }
  } else if (GetKeyState(VK_SHIFT) >> 1) {
    switch (iEvent.vkCode) {
    case VK_LEFT:
      alterSelection(Bounds::get(-5, 0, 0, 0));
      break;
    case VK_RIGHT:
      alterSelection(Bounds::get(5, 0, 0, 0));
      break;
    case VK_UP:
      alterSelection(Bounds::get(0, -5, 0, 0));
      break;
    case VK_DOWN:
      alterSelection(Bounds::get(0, 5, 0, 0));
      break;
    case 'O':
      {
        Bounds region;
        INT count = 0;

        node = firstNode[mLayer];

        while (node) {
          if (isSpriteSelected(node)) {
            if (count)
              region.setUnion(node->bounds);
            else
              region = node->bounds;

            ++count;
          }

          node = node->next;
        }

        Point middle = region.point + Point(region.size / 2L);
        INT radius = max(region.cx, region.cy) / 2;
        INT i = 0;

        node = firstNode[mLayer];

        while (node) {
          if (isSpriteSelected(node)) {
            REAL angle = i * 360.0f / count;

            appendUndo(node, UndoEdit);

            node->bounds.x =
              (LONG)(middle.x + cos(angle * PI / 180) * radius) -
              node->bounds.cx / 2;
            node->bounds.y =
              (LONG)(middle.y + sin(angle * PI / 180) * radius) -
              node->bounds.cy / 2;

            ++i;
          }

          node = node->next;
        }

        closeUndo();
      }
      break;
    case 'R':
      node = firstNode[mLayer];

      while (node)
      {
        if (isSpriteSelected(node))
        {
          appendUndo(node, UndoEdit);
          node->viewBounds.size = node->bounds.size = node->source.size;
          node->rotate = node->viewRotate = 0.0f;
        }

        node = node->next;
      }

      closeUndo();
      break;
    }
  } else {
    switch (iEvent.vkCode) {
    case VK_DELETE:
      deleteSelection();
      break;
    case VK_LEFT:
      alterSelection(Bounds::get(-1, 0, 0, 0));
      break;
    case VK_RIGHT:
      alterSelection(Bounds::get(1, 0, 0, 0));
      break;
    case VK_UP:
      alterSelection(Bounds::get(0, -1, 0, 0));
      break;
    case VK_DOWN:
      alterSelection(Bounds::get(0, 1, 0, 0));
      break;
    case VK_ESCAPE:
      {
        SpriteNode* sel = firstNode[mLayer];

        while (sel) {
          if (isSpriteSelected(sel))
          {
            sel->viewBounds = sel->bounds;
            sel->viewRotate = sel->rotate;
          }

          sel = sel->next;
        }
      }

      mSelectMode = SelectModeNone;
      mOvered = NULL;
      invalidate(FALSE);
      break;
    }
  }

  MapEditor::onKeyDown(iEvent);
}

VOID  SpriteEditor::onFocus(Event::FocusEvent& iEvent)
{
  if (!iEvent.focused)
  {
    mSelectMode = SelectModeNone;
    invalidate(FALSE);
  }
}

VOID  SpriteEditor::onToolbar(ToolBarNotify* iData)
{
  switch (iData->itemID)
  {
  case MapItemToggleSnap:
    setGridSnap(!getGridSnap());
    break;
  case MapItemGridSize:
    setGridSize(iData->value);
    break;
  default:
    MapEditor::onToolbar(iData);
    break;
  }
}

// RENDERING ------------------------------------------------------------------

VOID  SpriteEditor::drawForeground()
{
  MapEditor::drawForeground();

  drawNodeSelection();
  drawSelection();
  drawSnapGlyphs();
  drawRotateGlyphs();
  drawAnchors();
}

VOID  SpriteEditor::drawLayer(INT iLayer)
{
  SpriteNode* wNode;

  // draw copied selection
  if (this->mLayer == iLayer)
  {
    if ((mSelectMode == SelectModeDrag ||
      mSelectMode == SelectModeResize ||
      mSelectMode == SelectModeRotate) &&
      IsCopySelection())
    {
      wNode = firstNode[iLayer];

      while (wNode)
      {
        if (isSpriteSelected(wNode))
        {
          Bounds nodeBounds = wNode->bounds;
          nodeBounds.absolute();

          if (nodeBounds.intersects(mView))
          {
            DrawSpriteNode(
              getDevice(),
              wNode,
              wNode->bounds,
              Point::get(-mView.x, -mView.y));
          }
        }

        wNode = wNode->next;
      }
    }
  }

  wNode = firstNode[iLayer];

  // draw nodes
  while (wNode)
  {
    if (!(wNode->flags & SPRITENODE_HIDDEN))
    {
      if (Bounds::get(wNode->viewBounds).absolute().intersects(mView))
      {
        DrawSpriteNode(
          getDevice(),
          wNode,
          wNode->viewBounds,
          Point::get(-mView.x, -mView.y));
      }
    }

    wNode = wNode->next;
  }

  // draw node preview
  if (this->mLayer == iLayer && isMouseOver() &&
    getTool() != SpriteToolSelect && mSelectMode != SelectModeAdd)
  {
    Bounds wDest;
    getTilePreview(wDest);

    getDevice()->setAlpha(0xFF);

    switch (mNodeType)
    {
    case Data::SpriteTexture:
      if (mNodeResource && mNodeResource->getType() == Data::ResourceTexture)
      {
        Device::Display::Texture* wTexture = getDevice()->createTexture(
          ((Data::Texture*)mNodeResource)->getPath());

        getDevice()->beginBatch(wTexture);
        getDevice()->addBatch(
          wDest.point - mView.point,
          &mNodeSource,
          0xFFFFFFFF);

        wTexture->unref();
      }
      break;
    case Data::SpriteRect:
      getDevice()->beginBatch(NULL);
      getDevice()->addBatch(
        wDest - mView.point,
        NULL,
        mNodeColors[0],
        mNodeColors[1],
        mNodeColors[2],
        mNodeColors[3]);
      break;
    }
  }
}

VOID  SpriteEditor::drawNodeSelection()
{
  SpriteNode* node;

  Bounds select = mSelection;
  select.absolute();

  getDevice()->beginLine(2.0f);

  // draw overed node
  if (mOvered && mView.intersects(Bounds::get(mOvered->viewBounds).absolute())) {
    getDevice()->drawRectangle(
      Bounds::get(mOvered->viewBounds).inflate(-2, -2) - mView.point,
      0xFFFFFFFF);
  }

  // draw node selection preview
  if (!select.isEmpty() && mSelectMode == SelectModeSelect) {
    node = firstNode[mLayer];

    while (node) {
      Bounds nodeBounds = node->viewBounds;
      nodeBounds.absolute();

      if (isSelectable(node) &&
        select.intersects(nodeBounds) &&
        mView.intersects(nodeBounds))
      {
        if (!IsRemoveSelection() && !isSpriteSelected(node)) {
          getDevice()->drawRectangle(nodeBounds - mView.point,
            0xFF000000);
        }
      }

      node = node->next;
    }
  }

  node = firstNode[mLayer];

  // draw current node selection
  while (node) {
    if (isSpriteSelected(node) &&
      !(select.intersects(node->bounds) &&
      mSelectMode == SelectModeSelect && IsRemoveSelection()) &&
      mView.intersects(node->bounds))
    {
      getDevice()->drawRectangle(node->viewBounds - mView.point, 0xFF000000);
    }

    node = node->next;
  }

  // draw tile preview borders
  if (getTool() != SpriteToolSelect && isMouseOver() &&
    mSelectMode != SelectModeAdd && mNodeResource)
  {
    Bounds dest;
    getTilePreview(dest);

    getDevice()->drawRectangle(dest - mView.point, 0xFF000000);
  }
}

VOID  SpriteEditor::drawSelection()
{
  if (!mSelection.isEmpty() && mSelectMode == SelectModeSelect)
  {
    Bounds select = mSelection;
    select.absolute();

    // draw selection bounds
    getDevice()->beginLine(1.0f, 0xF0F0F0F0);
    getDevice()->drawRectangle(
      select - mView.point,
      0xFFFFFFFF);
    getDevice()->drawRectangle(
      Bounds::get(select).inflate(-1, -1) - mView.point,
      0xFF000000);

    getDevice()->beginTexture();
    getDevice()->fillRectangle(select - mView.point, 0x3FFFFFFF);
  }
}

VOID  SpriteEditor::drawSnapGlyphs()
{
  getDevice()->beginLine(1.0f, 0xF0F0F0F0);

  if (snapNodeX)
  {
    getDevice()->drawLine(
      snapPoint.x - mView.x, 0,
      snapPoint.x - mView.x, getBufferHeight(),
      0xFFFFFFFF);

    getDevice()->drawRectangle(
      snapNodeX->bounds - mView.point,
      0xFFFFFFFF);
  }

  if (snapNodeY)
  {
    getDevice()->drawLine(
      0, snapPoint.y - mView.y,
      getBufferWidth(), snapPoint.y - mView.y,
      0xFFFFFFFF);

    getDevice()->drawRectangle(
      snapNodeY->bounds - mView.point,
      0xFFFFFFFF);
  }
}

VOID  SpriteEditor::drawAnchors()
{
  Bounds select = mSelection;
  select.absolute();

  if (isSpriteSelected(mOvered) &&
    !(select.intersects(mOvered->bounds) &&
    mSelectMode == SelectModeSelect && IsRemoveSelection()) &&
    mView.intersects(mOvered->bounds))
  {
    Bounds corner;

    getDevice()->beginTexture();

    for (int i = (int)AnchorFirst; i <= (int)AnchorLast; ++i)
    {
      corner = mOvered->viewBounds - mView.point;
      getAnchorBounds(corner, (Anchor)i);

      getDevice()->fillRectangle(corner, 0xFF000000);

      if (i == (int)AnchorNW || i == (int)AnchorAxisY)
      {
        corner.inflate(-2, -2);
        getDevice()->fillRectangle(corner, 0xFFFFFFFF);
      }
    }
  }
}

VOID  SpriteEditor::drawRotateGlyphs()
{
  if (!mOvered || !isSpriteSelected(mOvered))
    return;

  const Vector::Vector2D wOrigin(
    (REAL)mOvered->viewBounds.x + mOvered->viewBounds.cx / 2,
    (REAL)mOvered->viewBounds.y + mOvered->viewBounds.cy / 2);

  Vector::Vect2D wAxisX;
  Vector::Vect2D wAxisY;
  Vector::Vect2D wPoint;

  wPoint.x  = (REAL)mOvered->viewBounds.getRight();
  wPoint.y  = wOrigin.y;
  wAxisX    = wPoint.rotate(wOrigin, mOvered->viewRotate);

  wPoint.x  = wOrigin.x;
  wPoint.y  = (REAL)mOvered->viewBounds.y;
  wAxisY    = wPoint.rotate(wOrigin, mOvered->viewRotate);

  getDevice()->beginLine(2.0f);
  getDevice()->drawLine(
    (LONG)wOrigin.x + -mView.x,
    (LONG)wOrigin.y + -mView.y,
    (LONG)wAxisX.x  + -mView.x,
    (LONG)wAxisX.y  + -mView.y,
    0xFF000000);
  getDevice()->drawLine(
    (LONG)wOrigin.x + -mView.x,
    (LONG)wOrigin.y + -mView.y,
    (LONG)wAxisY.x  + -mView.x,
    (LONG)wAxisY.y  + -mView.y,
    0xFF000000);
}

// RESOURCES ------------------------------------------------------------------

VOID  SpriteEditor::onUninitializeDisplay()
{
  getDevice()->getBuffer().clear();
}

VOID  SpriteEditor::onPrepareDisplay()
{
  getDevice()->setWidth(SPRITEMAP->getWidth());
  getDevice()->setHeight(SPRITEMAP->getHeight());
}

VOID  SpriteEditor::onInitializeDisplay()
{
  InitializeSpriteNodes(
    getDevice(),
    firstNode,
    mMapLayers,
    &Frame::get()->getProject());
}

BOOL  SpriteEditor::recreate()
{
  return FALSE;
}

VOID  SpriteEditor::initToolbar()
{
  // create tool menu
  getToolMenu().clear();
  getToolMenu().append(SpriteToolPen,     _T("Pen"));
  getToolMenu().append(SpriteToolRect,    _T("Rect"));
  getToolMenu().append(SpriteToolTile,    _T("Tile"));
  getToolMenu().append(SpriteToolSelect,  _T("Select"));

  // grid size values
  ItemDataNumeric num = { 10, 100, 2, getGridSize() };

  // toolbar items
  ToolItem items[] = {
    {MapItemToggleGrid,   _T("Grid"), ToolItemButton},
    {MapItemToggleSnap,   _T("Snap"), ToolItemButton},
    {0,                   _T("Of"),   ToolItemLabel},
    {MapItemGridSize,     _T(""),     ToolItemNumeric,
    &num,                 55 },
    {0,                   _T("px"),   ToolItemLabel},
    {0,                   _T(""),     ToolItemBreak},
    {0,                   _T("Edit"), ToolItemLabel},
    {MapItemLayer,        _T(""),     ToolItemMenu,
    menuLayers},
    {MapItemDisplay,      _T(""),     ToolItemMenu,
    menuDisplay,          0, TOOLITEM_AUTORENAME},
    {0,                   _T(""),     ToolItemBreak},
    {0,                   _T("With"), ToolItemLabel},
    {MapItemTool,         _T(""),     ToolItemMenu,
    getToolMenu(),        0, TOOLITEM_AUTORENAME},
    {0,                   _T("At"),   ToolItemLabel},
    {EditorItemZoom,      _T(""),     ToolItemMenu,
    getMenuZoom()},
  };

  getToolBar()->addItems(items, sizeof(items) / sizeof(ToolItem));
  getToolBar()->setItemChecked(MapItemToggleGrid, mShowGrid);
  getToolBar()->setItemChecked(MapItemToggleSnap, mSnapToGrid);

  setTool(SpriteToolSelect);

  MapEditor::initToolbar();
}

// GETTERS & SETTERS ----------------------------------------------------------

VOID  SpriteEditor::setLayer(INT value)
{
  MapEditor::setLayer(value);

  if (mLayer != value)
  {
    deselectAll();
    mSelectMode = SelectModeNone;
  }
}

VOID  SpriteEditor::setGridSnap(BOOL value)
{
  mSnapToGrid = value;

  if (getToolBar())
    getToolBar()->setItemChecked(Editor::MapItemToggleSnap, value);
  sendNotify(SpriteNotifySnap);
}

// UNDO & REDO ----------------------------------------------------------------

BOOL  SpriteEditor::canUndo()
{
  return undoCurr != NULL;
}

BOOL  SpriteEditor::canRedo()
{
  if (undoCurr) return undoCurr->next != NULL;
  return undoNode != NULL;
}

BOOL  SpriteEditor::undo()
{
  if (undoCurr) {
    SpriteUndo* undo = undoCurr->child;

    if (undo) {
      SpriteUndo* prev;

      while (undo->next) undo = undo->next;

      do {
        performUndo(undo, FALSE);

        prev = undo;
        undo = undo->prev;
      } while (undo->child != prev);
    }

    deselectAll();
    performUndo(undoCurr, FALSE);

    getMap()->setUnsaved();
    undoCurr = undoCurr->prev;
    undoEdit = NULL;

    invalidate(FALSE);

    sendNotify(EditorNotifyUndo);
    return TRUE;
  }

  return FALSE;
}

BOOL  SpriteEditor::redo()
{
  if (undoCurr) {
    if (undoCurr->next)
      undoCurr = undoCurr->next;
    else
      return FALSE;
  } else if (undoNode) {
    undoCurr = undoNode;
  } else {
    return FALSE;
  }

  SpriteUndo* undo = undoCurr->child;

  deselectAll();
  performUndo(undoCurr, TRUE);

  while (undo) {
    performUndo(undo, TRUE);
    undo = undo->next;
  }

  getMap()->setUnsaved();
  undoEdit = NULL;
  invalidate(FALSE);

  sendNotify(EditorNotifyRedo);
  return TRUE;
}

VOID  SpriteEditor::performUndo(SpriteUndo* value, BOOL redo)
{
  SpriteNode* node;
  SpriteUndo::Data temp;

  switch (value->action) {
  case UndoAdd:
  case UndoDelete:
    if ((value->action == UndoAdd) == redo) {
      value->flags &= ~SPRITEUNDOFLAG_DELETED;
      insertNode(value->node, value->data.node);
    } else {
      value->flags |= SPRITEUNDOFLAG_DELETED;
      value->data.node = value->node->prev;
      removeNode(value->node);
    }
    break;
  case UndoInsert:
    node = value->data.node;
    value->data.node = value->node->prev;

    removeNode(value->node);
    insertNode(value->node, node);
    break;
  case UndoEdit:
    temp.bounds = value->node->viewBounds;
    temp.rotate = value->node->viewRotate;

    value->node->bounds = value->node->viewBounds = value->data.bounds;
    value->node->rotate = value->node->viewRotate = value->data.rotate;

    value->data = temp;
    break;
  }

  value->node->flags |= SPRITENODE_SELECTED;
}

BOOL  SpriteEditor::save(HANDLE iFile)
{
  Data::SpriteMap wMap;

  memcpy(&wMap, SPRITEMAP, sizeof(wMap));
  wMap.buffer = NULL;

  flush(&wMap);

  BOOL wResult = wMap.save(iFile);

  wMap.free();
  return wResult;
}

VOID  SpriteEditor::setBufferSize(INT cx, INT cy, INT layers)
{
  if (mMapWidth != cx || mMapHeight != cy || mMapLayers != layers) {
    mMapWidth = cx;
    mMapHeight = cy;
    mMapLayers = layers;

    recreate();
  }
}

VOID  SpriteEditor::flush(Data::Resource* oResource)
{
  Data::SpriteMap* wMap = (Data::SpriteMap*)oResource;

  wMap->setSize(mMapWidth, mMapHeight, mMapLayers);

  SetSpriteNodes(wMap, firstNode);
}

VOID  SpriteEditor::fillMapBuffer()
{
  GetSpriteNodes(SPRITEMAP, firstNode);
}

VOID  SpriteEditor::freeMapBuffer()
{
  FreeSpriteNodes(firstNode, mMapLayers);
  firstNode = NULL;
}

VOID  SpriteEditor::createMapBuffer()
{
  firstNode   = new SpriteNode*[mMapLayers];
  memset(firstNode, 0, sizeof(firstNode[0]) * mMapLayers);

  snapNodeX   = NULL;
  snapNodeY   = NULL;
  undoNode    = NULL;
  undoEdit    = NULL;
  deletedNode = NULL;
  resize      = AnchorNone;

  mDragPoint.setEmpty();

  fillMapBuffer();
}

VOID  SpriteEditor::deleteUndo(SpriteUndo* value)
{
  if (value->flags & SPRITEUNDOFLAG_DELETED)
  {
    value->node->next = deletedNode;
    deletedNode = value->node;
  }

  delete value;
}

VOID  SpriteEditor::clearUndo(SpriteUndo* from)
{
  if (!from) return;

  if (from->prev)         from->prev->next = NULL;
  if (from == undoCurr)   undoCurr = undoNode = NULL;

  while (from) {
    SpriteUndo* nextState = from->next;
    SpriteUndo* nextChild = from->child;

    clearUndo(nextChild);
    deleteUndo(from);

    from = nextState;
  }
}

VOID  SpriteEditor::clearUndo()
{
  deletedNode = NULL;
  clearUndo(undoNode);
  undoNode = undoCurr = NULL;
}

VOID  SpriteEditor::clearCopy()
{
}

SpriteEditor::SpriteUndo* SpriteEditor::createUndo(
  SpriteNode* value,
  UndoAction action
  )
{
  SpriteUndo* undo = new SpriteUndo;

  undo->action    = action;
  undo->layer     = mLayer;
  undo->node      = value;
  undo->next      = NULL;
  undo->child     = NULL;
  undo->flags     = 0;

  switch (action) {
  case UndoDelete:
    undo->flags |= SPRITEUNDOFLAG_DELETED;
  case UndoAdd:
  case UndoInsert:
    undo->data.node = value->prev;
    break;
  case UndoEdit:
    undo->data.bounds = value->bounds;
    undo->data.rotate = value->rotate;
    break;
  }

  return undo;
}

VOID  SpriteEditor::appendUndo(SpriteNode* value, UndoAction action)
{
  deletedNode = NULL;

  if (undoCurr) {
    if (undoEdit) {
      SpriteUndo** after;

      if (undoEdit == undoCurr)
        after = &undoEdit->child;
      else
        after = &undoEdit->next;

      *after = createUndo(value, action);
      (*after)->prev = undoEdit;
      undoEdit = *after;
    } else {
      clearUndo(undoCurr->next);

      undoCurr->next = createUndo(value, action);
      undoCurr->next->prev = undoCurr;
      undoEdit = undoCurr = undoCurr->next;
    }
  } else {
    clearUndo(undoNode);

    undoNode = undoEdit = undoCurr = createUndo(value, action);
    undoNode->prev = NULL;
  }
}

// NODES ----------------------------------------------------------------------

VOID  SpriteEditor::addNode(
  const Geom::Bounds& source,
  const Geom::Bounds& dest
  )
{
  if (source.isEmpty() || dest.isEmpty() || !mNodeResource)
    return;

  SpriteNode* node = new SpriteNode;

  memset(node, 0, sizeof(SpriteNode));

  node->source      = source;
  node->bounds      = dest;
  node->viewBounds  = dest;
  node->flags       = SPRITENODE_SELECTED;
  node->source      = mNodeSource;
  node->type        = mNodeType;

  switch (mNodeType)
  {
  case Data::SpriteTexture:
    if (mNodeResource && mNodeResource->getType() == Data::ResourceTexture)
    {
      node->id      = mNodeResource->getID();
      node->texture = getDevice()->createTexture(
        ((Data::Texture*)mNodeResource)->getPath());
    }
    break;
  case Data::SpriteRect:
    for (UINT i = 0; i < ARRAYSIZE(node->color); ++i)
      node->color[i] = mNodeColors[i];
    break;
  }

  if (getTool() == SpriteToolTile)
    node->flags |= SPRITEDATA_TILE;

  calcEditBounds(node->bounds, SelectModeDrag);
  mSelectMode = SelectModeAdd;

  SpriteNode* wPrevNode = firstNode[mLayer];

  while (wPrevNode && wPrevNode->next)
    wPrevNode = wPrevNode->next;

  deselectAll();
  insertNode(node, wPrevNode);
  appendUndo(node, UndoAdd);

  closeUndo();
}

VOID  SpriteEditor::insertNode(SpriteNode* value, SpriteNode* after)
{
  SpriteNode* next;

  if (after) {
    next = after->next;
    after->next = value;
  } else {
    next = firstNode[mLayer];
    firstNode[mLayer] = value;
  }

  if (next) next->prev = value;

  value->prev       = after;
  value->next       = next;
  value->viewBounds = value->bounds;
}

VOID  SpriteEditor::removeNode(SpriteNode* value)
{
  if (value->prev)
    value->prev->next = value->next;
  else
    firstNode[mLayer] = value->next;

  if (value->next)
    value->next->prev = value->prev;
}

VOID  SpriteEditor::selectNode(SpriteNode* value)
{
  value->flags  |= SPRITENODE_SELECTED;

  invalidate(FALSE);
  sendNotify(EditorNotifySelect);
}

VOID  SpriteEditor::deselectNode(SpriteNode* value)
{
  value->flags &= ~SPRITENODE_SELECTED;

  invalidate(FALSE);
  sendNotify(EditorNotifySelect);
}

VOID  SpriteEditor::alterSelection(const Geom::Bounds &value)
{
  SpriteNode* node = firstNode[mLayer];

  while (node) {
    if (isSpriteSelected(node)) {
      appendUndo(node, UndoEdit);

      node->bounds += value;
      node->viewBounds = node->bounds;
    }

    node = node->next;
  }

  closeUndo();
}

VOID  SpriteEditor::deleteSelection()
{
  SpriteNode* node = firstNode[mLayer];

  while (node) {
    if (isSpriteSelected(node)) {
      appendUndo(node, UndoDelete);
      removeNode(node);
    }

    node = node->next;
  }

  closeUndo();
}

VOID  SpriteEditor::setSelectionFlags(INT set, INT unset)
{
  SpriteNode* node = firstNode[mLayer];

  while (node) {
    if (isSpriteSelected(node)) {
      appendUndo(node, UndoEdit);
      node->flags = (node->flags | set) & ~unset;
    }

    node = node->next;
  }

  closeUndo();
}

VOID  SpriteEditor::calcEditBounds(Geom::Bounds &value, SelectMode mode)
{
  switch (mode)
  {
  case SelectModeDrag:
    value += mCurrPoint - mDragPoint;

    if (getGridSnap()) snapToGrid(value);
    if (!IsFreeSelection()) snapToNodes(value);
    break;
  case SelectModeResize:
    INT margin = 0;

    if (resize == AnchorNW || resize == AnchorSW)
    {
      value.x += mCurrPoint.x - mDragPoint.x;
      value.cx -= mCurrPoint.x - mDragPoint.x;

      if (getGridSnap())
      {
        margin = findMargin(value.x, getGridSize());

        if (abs(margin) < SNAP_THRESHOLD)
        {
          value.x -= margin;
          value.cx += margin;
        }
      }
    }
    else
    {
      value.cx += mCurrPoint.x - mDragPoint.x;

      if (getGridSnap())
      {
        margin = findMargin(value.getRight(), getGridSize());

        if (abs(margin) < SNAP_THRESHOLD)
          value.cx -= margin;
      }
    }

    if (resize == AnchorNW || resize == AnchorNE)
    {
      value.y += mCurrPoint.y - mDragPoint.y;
      value.cy -= mCurrPoint.y - mDragPoint.y;

      if (getGridSnap())
      {
        margin = findMargin(value.y, getGridSize());

        if (abs(margin) < SNAP_THRESHOLD)
        {
          value.y -= margin;
          value.cy += margin;
        }
      }
    }
    else
    {
      value.cy += mCurrPoint.y - mDragPoint.y;

      if (getGridSnap())
      {
        margin = findMargin(value.getBottom(), getGridSize());

        if (abs(margin) < SNAP_THRESHOLD)
          value.cy -= margin;
      }
    }
    break;
  }
}

VOID  SpriteEditor::calcEditRotate(
  const Geom::Bounds &bounds,
  REAL &value,
  SelectMode mode
  )
{
  switch (mode)
  {
  case SelectModeRotate:
    value -= (REAL)mDragPoint.angle(
      Geom::Point::get(bounds.x + bounds.cx / 2, bounds.y + bounds.cy / 2),
      mCurrPoint);

    while (value > 2 * PI) value -= 2 * PI;
    while (value < -2 * PI) value += 2 * PI;

    if (GetKeyState(VK_MENU) >> 1)
    {
      INT wRemainder = (INT)DEGREE(value) % 45;

      if (wRemainder > 22)
        value += RADIAN(45 - wRemainder);
      else
        value -= RADIAN(wRemainder);
    }
    break;
  }
}

VOID  SpriteEditor::snapToGrid(Geom::Bounds &value)
{
  RECT margins = {
    findMargin(value.x, getGridSize()),
    findMargin(value.y, getGridSize()),
    findMargin(value.getRight(), getGridSize()),
    findMargin(value.getBottom(), getGridSize())
  };

  if (abs(margins.left) < abs(margins.right))
  {
    if (abs(margins.left) < SNAP_THRESHOLD)
      value.x -= margins.left;
  }
  else
  {
    if (abs(margins.right) < SNAP_THRESHOLD)
      value.x -= margins.right;
  }

  if (abs(margins.top) < abs(margins.bottom))
  {
    if (abs(margins.top) < SNAP_THRESHOLD)
      value.y -= margins.top;
  }
  else
  {
    if (abs(margins.bottom) < SNAP_THRESHOLD)
      value.y -= margins.bottom;
  }
}

VOID  SpriteEditor::snapToNodes(Geom::Bounds &value)
{
  SpriteNode* node    = firstNode[mLayer];
  POINT       margins = { SNAP_THRESHOLD, SNAP_THRESHOLD };
  snapNodeX           = NULL;
  snapNodeY           = NULL;

  while (node) {
    /*if (!isSpriteSelected(node))*/ {
      BOOL xNodeFar = FALSE;
      BOOL yNodeFar = FALSE;

      POINT nodeMargins = {
        findMargin(
        value.x, value.getRight(),
        node->bounds.x, node->bounds.getRight(),
        xNodeFar),
        findMargin(
        value.y, value.getBottom(),
        node->bounds.y, node->bounds.getBottom(),
        yNodeFar)
      };

      POINT diff = {
        abs(nodeMargins.x) - abs(margins.x),
        abs(nodeMargins.y) - abs(margins.y)
      };

      if (!diff.x && snapNodeX) {
        diff.x =
          abs((node->bounds.y + node->bounds.cy / 2) -
          (value.y + value.cy / 2)) -
          abs((snapNodeX->bounds.y + snapNodeX->bounds.cy / 2) -
          (value.y + value.cy / 2));
      }

      if (!diff.y && snapNodeY) {
        diff.y =
          abs((node->bounds.x + node->bounds.cx / 2) -
          (value.x + value.cx / 2)) -
          abs((snapNodeY->bounds.x + snapNodeY->bounds.cx / 2) -
          (value.x + value.cx / 2));
      }

      if (diff.x < 0) {
        margins.x       = nodeMargins.x;
        snapPoint.x     = xNodeFar ? value.getRight() - 1 : value.x;
        snapPoint.x    += margins.x;
        snapNodeX       = node;
      }

      if (diff.y < 0) {
        margins.y       = nodeMargins.y;
        snapPoint.y     = yNodeFar ? value.getBottom() -1 : value.y;
        snapPoint.y    += margins.y;
        snapNodeY       = node;
      }
    }

    node = node->next;
  }

  if (margins.x < SNAP_THRESHOLD)
    value.x += margins.x;
  if (margins.y < SNAP_THRESHOLD)
    value.y += margins.y;
}

BOOL  SpriteEditor::hitTestNode(SpriteNode* value, const Geom::Point& pt)
{
  if (!isSelectable(value)) return FALSE;

  Bounds nodeBounds = value->bounds;

  nodeBounds.absolute();
  nodeBounds.inflate(ANCHORSIZE / 2, ANCHORSIZE / 2);

  return nodeBounds.contains(pt);
}

VOID  SpriteEditor::getAnchorBounds(
  Geom::Bounds &ioBounds,
  Anchor iAnchor
  )
{
  if (iAnchor == AnchorAxisX || iAnchor == AnchorAxisY)
  {
    const Vector::Vector2D wOrigin(
      (REAL)ioBounds.x + ioBounds.cx / 2,
      (REAL)ioBounds.y + ioBounds.cy / 2);

    Vector::Vect2D wPoint;
    Vector::Vect2D wAxis;

    switch (iAnchor)
    {
    case AnchorAxisX:
      wPoint.x = (REAL)ioBounds.getRight();
      wPoint.y = wOrigin.y;
      break;
    case AnchorAxisY:
      wPoint.x = wOrigin.x;
      wPoint.y = (REAL)ioBounds.y;
      break;
    }

    wAxis = wPoint.rotate(wOrigin, mOvered->viewRotate);
    ioBounds.x = (LONG)wAxis.x - ANCHORSIZE / 2;
    ioBounds.y = (LONG)wAxis.y - ANCHORSIZE / 2;
  }
  else
  {
    ioBounds.point = ioBounds.point - (ANCHORSIZE / 2);

    switch (iAnchor)
    {
    case AnchorNW:
      break;
    case AnchorNE:
      ioBounds.point = ioBounds.point + Point::get(ioBounds.cx, 0);
      break;
    case AnchorSW:
      ioBounds.point = ioBounds.point + Point::get(0, ioBounds.cy);
      break;
    case AnchorSE:
      ioBounds.point = ioBounds.point + Point::get(ioBounds.size);
      break;
    }
  }

  ioBounds.size = Size::get(ANCHORSIZE, ANCHORSIZE);
}

VOID  SpriteEditor::getTilePreview(Geom::Bounds &oValue)
{
  switch (getTool())
  {
  case SpriteToolPen:
    oValue = Bounds::get(mNodeSource.size / -2L, mNodeSource.size);
    break;
  default:
    oValue = Bounds::get(0, 0, mNodeSource.cx, mNodeSource.cy);
    break;
  }

  calcEditBounds(oValue, SelectModeDrag);
}

VOID  SpriteEditor::selectAll()
{
  if (!firstNode) return;

  SpriteNode* node = firstNode[mLayer];

  while (node)
  {
    if (!(node->flags & SPRITENODE_LOCKED))
      node->flags |= SPRITENODE_SELECTED;

    node = node->next;
  }

  invalidate(FALSE);
  sendNotify(EditorNotifySelect);
}

VOID  SpriteEditor::deselectAll()
{
  if (!firstNode) return;

  SpriteNode* node = firstNode[mLayer];

  while (node) {
    node->flags &= ~SPRITENODE_SELECTED;
    node = node->next;
  }

  invalidate(FALSE);
  sendNotify(EditorNotifySelect);
}

VOID  SpriteEditor::groupSelection()
{
  SpriteNode* node    = firstNode[mLayer];
  SpriteNode* group   = new SpriteNode;
  SpriteNode* insert;
  SpriteNode* last;

  group->child    = NULL;
  group->flags    = SPRITEDATA_GROUP | SPRITENODE_SELECTED;
  group->prev     = NULL;
  group->next     = NULL;

  while (node)
  {
    SpriteNode* next = node->next;

    if (isSpriteSelected(node))
    {
      appendUndo(node, UndoInsert);
      removeNode(node);

      if (group->child)
      {
        last->next      = node;
        node->prev      = last;

        group->bounds.setUnion(node->bounds);
      }
      else
      {
        group->child  = node;
        group->bounds = node->bounds;

        insert        = node->prev;
        node->prev    = group;
      }

      last = node;
      last->next = NULL;
    }

    node = next;
  }

  if (group->child)
  {
    group->viewBounds = group->bounds;
    node = group->child;

    do
    {
      appendUndo(node, UndoEdit);
      node->bounds -= group->bounds.point;
      node->viewBounds = node->bounds;

      node = node->next;
    }
    while (node);

    appendUndo(group, UndoAdd);
    insertNode(group, insert);

    closeUndo();
  }
  else
  {
    delete group;
  }
}

VOID  SpriteEditor::ungroupSelection()
{
}
